package com.example.treeutil.tree;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;

import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;

/**
 * 2020年10月25日
 * 树结构组装工具 <br>
 * 提供了三个方法，1: 同类型的对象集合组织成树结构 <br>
 * 2: 实现Node接口的不同类型对象组织成树结构 <br>
 * 3: 通过NodeValue注解，实现不同类型对象组织成树结构
 * @author 王风雨
 */
@ApiModel
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TreeUtil<T, ID> {
    private final List<T> topList = new ArrayList<>();
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 适合单一对象集合组装成树结构，主要方法就是找到顶层的集合
     *
     * @param list        需要进行树型结构的对象集合，要求对象有符合节点特征的属性和方法
     * @param getId       获取对象ID的方法
     * @param getParentID 对象获取父对象的ID的方法
     * @param hasParent   对象是否有父对象的方法，方法返回true表示有父对象，false则表示对象为最上层，没有时默认用pid判断，建议自己指定此判断方法
     * @param addChild    父对象添加单个对象到子集的sub集合，默认从getSub方法获取子集后用add添加，建议子集指定实现添加子对象的方法
     * @param comparator  同级对象排序的方法，可以为空不排序
     * @param getSub      可以为null，对象获取子集的方法，如果提供了，在返回结果前将对所有子集进行排序，使用方法入参提供的排序器，为null时，对象最好自己维护排序
     * @return never {@literal null}
     */
    public Optional<List<T>> toTree(@NotNull List<T> list,
                                    @NotNull Function<T, ID> getId,
                                    @NotNull Function<T, ID> getParentID,
                                    @Nullable Function<T, Boolean> hasParent,
                                    @Nullable TreeUtil.AddChild<T> addChild,
                                    @Nullable Comparator<T> comparator,
                                    @Nullable Function<T, List<T>> getSub) {
        int mapSize = (int) (list.size() / DEFAULT_LOAD_FACTOR) + 1;
        Map<ID, T> map = new HashMap<>(mapSize);
        list.forEach(t -> {
            if (null == hasParent) {
                ID pid = getParentID.apply(t);
                if (null != pid) {
                    if (pid instanceof String) {
                        if (((String) pid).isEmpty()){
                            topList.add(t);
                        }
                    }
                } else {
                    topList.add(t);
                }
            } else {
                if (!hasParent.apply(t)) {
                    topList.add(t);
                }
            }
            map.put(getId.apply(t), t);
        });

        // 只需要找到每个对象的父，然后父把子加到父的子集中
        map.entrySet().stream().filter(entry -> hasParent.apply(entry.getValue())).forEach(entry -> {
            T t = entry.getValue();
            T parent = map.get(getParentID.apply(t));
            if (null != parent) {
                if (null != addChild)
                    addChild.addChild(parent, t);
                else {
                    getSub.apply(parent).add(t);
                }
            }
        });

        // 如果有获取子集的方法，则对所有子集进行排序
        if (null != comparator && null != getSub)
            sort(topList, getSub, comparator);
        return Optional.of(topList);
    }

    /**
     * 自包含对象给子集中添加要素的接口
     *
     * @param <T>
     */
    @FunctionalInterface
    public interface AddChild<T> {
        /**
         * 父对象的子集中加入一个子对象
         *
         * @param parent 父对象
         * @param child  子对象
         */
        void addChild(T parent, T child);
    }

    /**
     * 适合不同类型的对象组织成树结构，要求每个对象实现 NOde 接口
     * @param list<Node>
     * @return 树型结构的集合
     */
    public static Optional<List<Node>> toTree(final List<Node> list){
        return new TreeUtil<Node, Object>().toTree(list,
                Node::getId,
                Node::getPid,
                Node::hasParent,
                Node::addChild,
                Comparator.comparingLong(Node::getIndex),
                Node::getSubs);
    }

    public static <T, R> Optional<List<T>> toTree2(List<T> list,
                                            Function<T, R> getId,
                                            Function<T,R> getPid,
                                            Function<T, Boolean> hasParent,
                                            AddChild<T> addChild,
                                            Comparator<T> comparator,
                                            Function<T, R> getSubs) {

        return new TreeUtil().toTree(list, getId, getPid, hasParent, addChild,comparator, getSubs);
    }

    /**
     * 适合不同类型的对象组织成树结构，要求每个对象的相关字段和方法上有 NodeValue 注解，通过这些注解信息可获取组成树节点的必要属性
     * @param list<Object>
     * @return
     */
    public static Optional<List<Object>> toTreeByNodeTile(final List<Object> list) {
        return new TreeUtil<Object, Object>().toTree(list,
                getID(NodeValue.FIELD.ID),
                getID(NodeValue.FIELD.PID),

                /**
                 * 判断对象是否有父级节点的方法 *
                 */
                o -> {
                    Optional<Method> methodStream = findMethod(o, NodeValue.METHOD.HAS_PARENT);
                    if (methodStream.isPresent()) {
                        try {
                            return (boolean) methodStream.get().invoke(o);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                            return false;
                        }
                    } else {
                        Object pid = getID(NodeValue.FIELD.PID).apply(o);
                        return pid != null;
                    }
                },

                /**
                 * 父节点添加子对象的方法 *
                 */
                (parent, child) -> {
                    NodeValue nd = parent.getClass().getAnnotation(NodeValue.class);
                    if (nd == null || !nd.isCatalog()) return;
                    Optional<Method> optionalMethod = findMethod(parent, NodeValue.METHOD.ADD_CHILD);
                    if (optionalMethod.isPresent()) {
                        try {
                            optionalMethod.get().invoke(parent, child);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    } else  {
                        findMethod(parent, NodeValue.METHOD.GET_SUBS).ifPresent(method -> {
                            try {
                                List<Object> subs = (List) method.invoke(parent);
                                if (null != subs) subs.add(child);
                            } catch (IllegalAccessException | InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        });
                    }
                },

                /**
                 * 排序比较方法 *
                 */
                (o1, o2) -> {
                    long v1 = 0L, v2 = 0L;
                    Optional<Method> optionalMethod = findMethod(o1, NodeValue.METHOD.GET_INDEX);
                    if(optionalMethod.isPresent()) {
                        try {
                            v1 = Long.parseLong(optionalMethod.get().invoke(o1).toString());
                        } catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) {
                            e.printStackTrace();
                            return 0;
                        }
                    }
                    optionalMethod = findMethod(o2, NodeValue.METHOD.GET_INDEX);
                    if (optionalMethod.isPresent()) {
                        try {
                            v2 = Long.parseLong(optionalMethod.get().invoke(o2).toString());
                        } catch (IllegalAccessException | InvocationTargetException | NumberFormatException e) {
                            e.printStackTrace();
                            return 0;
                        }
                    }
                    return Long.compare(v1, v2);
                },

                /**
                 * 获取对象子集的方法 *
                 */
                o -> {
                    Optional<Method> optionalMethod = findMethod(o, NodeValue.METHOD.GET_SUBS);
                    if (optionalMethod.isPresent()) {
                        try {
                            return (List) optionalMethod.get().invoke(o);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                    return null;
                }
            );
    }

    /**
     * 排序
     *
     * @param list       要排序的集合
     * @param subs       集合中单个对象获取子集的方法
     * @param comparator 排序器
     */
    private void sort(List<T> list, Function<T, List<T>> subs, Comparator<T> comparator) {
        Optional.ofNullable(list).ifPresent(list1 -> {
            if (!list1.isEmpty()) {
                if (null != comparator) {
                    list1.sort(comparator);
                    list1.parallelStream().forEach(t -> sort(subs.apply(t), subs, comparator));
                }
            }
        });
    }

    private static Function getID(NodeValue.FIELD field){
        return o -> {
            Object id = null;
            Optional<Field> optionalField = Arrays.stream(o.getClass().getDeclaredFields())
                    .filter(field1 -> {
                        NodeValue anno = field1.getAnnotation(NodeValue.class);
                        return (null != anno && anno.field() == field);
                    })
                    .findAny();
            if (optionalField.isPresent()) {
                String fieldName = optionalField.get().getName();
                try {
                    Method method = o.getClass().getMethod("get" + fieldName.replace(String.valueOf(fieldName.charAt(0)), String.valueOf(fieldName.charAt(0)).toUpperCase()));
                    id = method.invoke(o);
                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            return id;
        };
    }

    private static Optional<Method> findMethod(Object o, NodeValue.METHOD method){
        return Arrays.stream(o.getClass().getMethods()).filter(m -> {
            NodeValue anno = m.getAnnotation(NodeValue.class);
            return (anno != null && anno.method() == method);
        }).findAny();
    }
}
